100万ページの巨大projectで激しくリンク記法を編集するとout of memoryでChromeがクラッシュしてしまう
巨大なprojectでリンク記法を書いたあとしばらくページ編集やカーソル移動が重いという問題があり、調査していますshokai.icon
まずはout of memoryを解決します
他の問題として、100万ページあるとQuickSearchが遅い等もあるけど、そっちは扱わない
実装
https://github.com/nota/scrapbox/pull/6265
準備:ローカル開発環境に100万ページの巨大projectを用意する
ローカル開発環境のScrapboxに大量のページを作るscriptを使う
ローカルに20万ページ作ってみた
ちょっと重かった
まだ足りない。ある会社は30万ページ + 5万空ページある
とりあえずローカルに100万ページ作ろうshokai.icon
すごいyuiseki.iconteramotodaiki.icon
こうなる…?
https://nota.gyazo.com/d3541d57117b8a6afc6ca745006041e2
なりましたshokai.icon
https://scrapbox.io/files/652a39394d2a72001cbb98e2.png
100万ページ + 34万空ページある
https://scrapbox.io/files/652a393cef4885001b0596d1.png
負荷をかけてout of memoryを発生させる方法
準備
mainブランチにて
simulateRequestDelayを無効化しておく
Workerの処理待ち中にlinks changeが来る確率が高まる
chrome devtoolを開いておく
巨大projectを開き、devtoolのconsole出力が静かになるまで待つ
100万ページぶんのpage titles APIが一旦CacheStorageに入るのを待つ
これは1回cacheに入ればよいshokai.icon
3分ぐらいかかる
バックグラウンドタブのWebWorkerをゆっくり動作させるがあるので、windowをactiveにしておかないともっと時間がかかる
リロードする
cache storageからQuickSearch Storeが復元されて、リンク記法の補完Popupが使えるようになる
同時に、また最新の100万ページぶんのpage titles APIのfetchも走っている
これで準備完了!
負荷をかける
以下の1〜3を繰り返すと、out of memoryが発生する
1. IMEオフにして、#aaaaabbbbbccccddddみたいなhashtagを素早く何度も変更する
キーボードをガチャガチャ打ったり、backspaceキーを何度も押したり
これをやると、page.linksが更新されて、サーバーに保存され、socket.ioでlinks changeが送られてきて、QuickSearchの辞書再作成が何度もWorkerに依頼される
2. カーソルをリンク記法がある行に上下から何度も侵入・離脱を繰り返す
3. IMEをオフにして、リンク記法の補完Popupでスペース区切りand検索を行う
特に3が効くかもしれない。1と2の要素も含んでいるのでshokai.icon
https://gyazo.com/0332df808bd8ce67544fb93df7d89464
最初のうちはわりとすぐPaused before potential out-of-memory crashで止まる
改善したらだんだん長生きするようになっていった
最新の100万ページぶんのpage titles APIのfetchが完了するまで耐えられるようになって
そのまま編集し続けられるようになった
クラッシュした箇所がDevToolsで見れるの知りませんでした…!有益情報だteramotodaiki.icon
まだクラッシュはしていません。クラッシュしそうな雰囲気を検知して、クラッシュ前に止まりますshokai.icon
以下、検出したout of memoryとその解決方法のメモ
100万ページを編集してると、mergeChunksToMessageでout of memoryが検出されたshokai.icon
https://gyazo.com/412e2c9a86f4510454d788483c699ea8
WebWorkerから複数回に分けて返ってくるpostMessageを、1つのobjectに結合する関数
変更
mergeChunksToMessageとsplitMessageToChunksをasync関数化した
今度はWorker内で使ってるlodashのlodash.uniqByでout of memoryが検出された
https://gyazo.com/e9f373732e8e84e81b1159b7cec37479
uniqByと同等の処理を自作した
クソデカ配列対策として、1000件おきにawait delay(1)を挟むようにした
まだmergeChunksToMessageがダメらしい
https://gyazo.com/a01f9477ea6746994a1b5f55edd72992
Object.entriesが悪いんじゃないか?shokai.icon
objectの全keyとプロパティの配列を作るので
デカいobjectでやると、デカい新規Arrayができる?
ちゃんと参照渡しになってるから問題なさそうだ
code:js
$ node
o = {a: 1,2,3,4}
{ a: 1, 2, 3, 4 }
for (const key, value of Object.entries(o)) { value.shift() }
1
o
{ a: 2, 3, 4 }
でもなんとなくfor inで書き直した。勢いでやったshokai.icon
msgObj[key] = msgObj[key].concat(value)は新規のArrayを作ってて怪しい
msgObj[key].push(...value)に書き直した
こっちは効いてそうshokai.icon
5分ぐらいずっと負荷をかけ続けていたら、for (const page of [..._pages, ...links])でout of memory発生
https://gyazo.com/61a8e5be4d2edcc05a0ae9e5411378de
spread operatorで2つの配列を連結するのは良くないようだ
全要素がコピーされた新規Arrayが作られる
これはブラウザ側がステップ実行状態にならなかった
Workerの中だから当然か
あと一歩という手応えがでてきたshokai.icon
修正
code:js
for (const arr of _pages, links) {
for (const page of arr) {
spread operatorで連結する必要なんて全く無かった
上の変更をぜんぶやった結果、out of memoryが発生しなくなったshokai.icon
https://github.com/nota/scrapbox/pull/6265
負けた。7分ぐらいずっと負荷かけてたらクラッシュした
https://gyazo.com/c01bc6862bf6650f004a0ffb8fbf47f8
Chrome devtoolsもout of memoryの可能性を検出できず、いきなり死んだ
まあ、最初の頃よりはだいぶマシになったのは確かだshokai.icon
学んだ事
デカいループはたまにawait delay(1)いれろ
Object.entriesは新規に配列を返すので怖い(使うな、という程ではない気がする)
配列の連結にspread operator使うな
lodash.uniqByやめろ
とにかく新規のArray作るのやめろ使いまわせ